// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

#include <QtCore/qrangemodeladapter.h>

#ifndef QT_NO_WIDGETS

using namespace Qt::StringLiterals;

#include <QtWidgets/qlistview.h>
#include <QtWidgets/qtableview.h>
#include <QtWidgets/qtreeview.h>
#include <vector>

class Book
{
    Q_GADGET
    Q_PROPERTY(QString title READ title)
    Q_PROPERTY(QString author READ author)
    Q_PROPERTY(QString summary MEMBER m_summary)
    Q_PROPERTY(int rating READ rating WRITE setRating)
public:
    enum Roles
    {
        TitleRole = Qt::UserRole,
        AuthorRole,
        SummaryRole,
        RatingRole,
    };

    Book() = default;
    Book(const QString &title, const QString &author);

    // C++ rule of 0: destructor, as well as copy/move operations
    // provided by the compiler.

    // read-only properties
    QString title() const { return m_title; }
    QString author() const { return m_author; }

    // read/writable property with input validation
    int rating() const { return m_rating; }
    void setRating(int rating)
    {
        m_rating = qBound(0, rating, 5);
    }

private:
    QString m_title;
    QString m_author;
    QString m_summary;
    int m_rating = 0;
};

template <> struct QRangeModel::RowOptions<Book>
{
    static constexpr auto rowCategory = QRangeModel::RowCategory::MultiRoleItem;
};

struct TreeRow;
using Tree = QList<TreeRow *>;

struct TreeRow
{
    QString firstColumn;
    int secondColumn = 0;

    TreeRow() = default;
    explicit TreeRow(const QString &first, int second, std::optional<Tree> children = std::nullopt)
        : firstColumn(first), secondColumn(second), m_children(children)
    {}

    TreeRow *parentRow() const { return m_parent; }
    void setParentRow(TreeRow *parent) { m_parent = parent; }
    const std::optional<Tree> &childRows() const { return m_children; }
    std::optional<Tree> &childRows() { return m_children; }

private:
    TreeRow *m_parent;
    std::optional<Tree> m_children;

    template <size_t I> friend inline decltype(auto) get(const TreeRow &row) {
        static_assert(I < 2);
        return false;
    }
};

namespace std {
    template<> struct tuple_size<TreeRow> : integral_constant<int, 2> {};
    template<> struct tuple_element<0, TreeRow> { using type = QString; };
    template<> struct tuple_element<1, TreeRow> { using type = int; };
}

void construct_and_use()
{
    //! [construct]
    std::vector<int> data = {1, 2, 3, 4, 5};
    QRangeModelAdapter adapter(&data);
    //! [construct]

    //! [use-model]
    QListView listView;
    listView.setModel(adapter.model());
    //! [use-model]
}

void get_and_set()
{
    QListView tableView;
    //! [get-range]
    QList<Book> books = {
        // ...
    };
    QRangeModelAdapter adapter(books);
    tableView.setModel(adapter.model());

    // show UI and where the user can modify the list

    QList<Book> modifiedBooks = adapter;
    // or
    modifiedBooks = adapter.range();
    //! [get-range]

    //! [set-range]
    // reset to the original
    adapter = books;
    // or
    adapter.setRange(books);
    //! [set-range]
}

void dataAccess()
{
    int row = 0;
    int column = 0;
    int path = 0;
    int to = 0;
    int branch = 0;

    QRangeModelAdapter listAdapter(QList<int>{});
    //! [list-data]
    QVariant listItem = listAdapter.data(row);
    //! [list-data]

    QRangeModelAdapter tableAdapter(QList<QList<int>>{});
    //! [table-data]
    QVariant tableItem = tableAdapter.data(row, column);
    //! [table-data]

    QRangeModelAdapter treeAdapter(QList<TreeRow *>{});
    //! [tree-data]
    QVariant treeItem = treeAdapter.data({path, to, branch}, column);
    //! [tree-data]

    //! [multirole-data]
    QRangeModelAdapter listOfBooks(QList<Book>{
        // ~~~
    });
    QString bookTitle = listOfBooks.data(0, Book::TitleRole).toString();
    Book multiRoleItem = listOfBooks.data(0).value<Book>();
    //! [multirole-data]
}

void list_access()
{
    QListView listView;
    {
        //! [list-access]
        QRangeModelAdapter list(std::vector<int>{1, 2, 3, 4, 5});
        listView.setModel(list.model());

        int firstValue = list.at(0); // == 1
        list.at(0) = -1;
        list.at(1) = list.at(4);
        //! [list-access]
    }
    {
        //! [list-access-multirole]
        QRangeModelAdapter books(QList<Book>{
            // ~~~
        });
        Book firstBook = books.at(0);
        Book newBook = {};
        books.at(0) = newBook; // dataChanged() emitted
        //! [list-access-multirole]

        //! [list-access-multirole-member-access]
        QString title = books.at(0)->title();
        //! [list-access-multirole-member-access]

        //! [list-access-multirole-write-back]
        // books.at(0)->setRating(5); - not possible even though 'books' is not const
        firstBook = books.at(0);
        firstBook.setRating(5);
        books.at(0) = firstBook; // dataChanged() emitted
        //! [list-access-multirole-write-back]
    }
}

void table_access()
{
    QTableView tableView;
    {
        //! [table-item-access]
        QRangeModelAdapter table(std::vector<std::vector<double>>{
            {1.0, 2.0, 3.0, 4.0, 5.0},
            {6.0, 7.0, 8.0, 9.0, 10.0},
        });
        tableView.setModel(table.model());

        double value = table.at(0, 2); // value == 3.0
        table.at(0, 2) = value * 2; // table[0, 2] == 6.0
        //! [table-item-access]

        //! [table-row-const-access]
        const auto &constTable = table;
        const std::vector<double> &topRow = constTable.at(0);
        //! [table-row-const-access]

        //! [table-row-access]
        auto lastRow = table.at(table.rowCount() - 1);
        lastRow = { 6.5, 7.5, 8.0, 9.0, 10 }; // emits dataChanged() for entire row
        //! [table-row-access]
    }

    {
        //! [table-mixed-type-access]
        QRangeModelAdapter table(std::vector<std::tuple<int, QString>>{
            // ~~~
        });
        int number = table.at(0, 0)->toInt();
        QString text = table.at(0, 1)->toString();
        //! [table-mixed-type-access]
    }
}

void tree_access()
{
    QTreeView treeView;

    //! [tree-row-access]
    QRangeModelAdapter tree = QRangeModelAdapter(Tree{
        new TreeRow{"Germany", 357002, Tree{
                new TreeRow("Bavaria", 70550)
            },
        },
        new TreeRow{"France", 632702},
    });
    treeView.setModel(tree.model());

    auto germanyData = tree.at(0);
    auto bavariaData = tree.at({0, 0});
    //! [tree-row-access]

    //! [tree-item-access]
    auto germanyName = tree.at(0, 0);
    auto bavariaSize = tree.at({0, 0}, 1);
    //! [tree-item-access]

    //! [tree-row-write]
    // deletes the old row - tree was moved in
    tree.at({0, 0}) = new TreeRow{"Berlin", 892};
    //! [tree-row-write]
}

void read_only()
{
#if 0
    //! [read-only]
    const QStringList strings = {"On", "Off"};
    QRangeModelAdapter adapter(strings);
    adapter.at(0) = "Undecided"; // compile error: return value of 'at' is const
    adapter.insertRow(0); // compiler error: requirements not satisfied
    //! [read-only]
#endif
}

void list_iterate()
{
    QRangeModelAdapter books(QList<Book>{
        // ~~~
    });
    QListView view;
    view.setModel(books.model());

    //! [ranged-for-const-list]
    for (const auto &book : std::as_const(books)) {
        qDebug() << "The book" << book.title()
                 << "written by" << book.author()
                 << "has" << book.rating() << "stars";
    }
    //! [ranged-for-const-list]

    //! [ranged-for-mutable-list]
    for (auto book : books) {
        qDebug() << "The book" << book->title()
                 << "written by" << book->author()
                 << "has" << book->rating() << "stars";

        Book copy = book;
        copy.setRating(copy.rating() + 1);
        book = copy;
    }
    //! [ranged-for-mutable-list]
}

void table_iterate()
{
    //! [ranged-for-const-table]
    QRangeModelAdapter table(std::vector<std::pair<int, QString>>{
        // ~~~
    });

    for (const auto &row : std::as_const(table)) {
        qDebug() << "Number is" << row->first << "and string is" << row->second;
    }
    //! [ranged-for-const-table]

    //! [ranged-for-const-table-items]
    for (const auto &row : std::as_const(table)) {
        for (const auto &item : row) {
            qDebug() << item; // item is a QVariant
        }
    }
    //! [ranged-for-const-table-items]

    //! [ranged-for-mutable-table]
    for (auto row : table) {
        qDebug() << "Number is" << row->first << "and string is" << row->second;
        row = std::make_pair(42, u"forty-two"_s);
    }
    //! [ranged-for-mutable-table]

    //! [ranged-for-mutable-table-items]
    for (auto row : table) {
        for (auto item : row) {
            item = 42;
        }
    }
    //! [ranged-for-mutable-table-items]
}

void tree_iterate()
{
    QRangeModelAdapter tree = QRangeModelAdapter(Tree{
        new TreeRow{"1", 1, Tree{
                new TreeRow("1.1", 11)
            },
        },
        new TreeRow{"2", 2},
    });

    static_assert(std::is_same_v<typename decltype(tree)::DataReference::value_type, QVariant>);

    //! [ranged-for-tree]
    for (auto row : tree) {
        if (row.hasChildren()) {
            for (auto child : row.children()) {
                // ~~~
            }
        }
    }
    //! [ranged-for-tree]
}

#endif
